﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Configuration;
using System.Security.Principal;
using System.IdentityModel.Claims;
using WcfContrib;
using WcfContrib.Handlers;
using WcfContrib.Configuration;

namespace WcfContrib.Security
{
    class WindowsAuthroziationManager : ServiceAuthorizationManager
    {
        #region WindowsMappingKey
        private struct WindowsMappingKey
        {
            private string operationFilter;
            public string OperationFilter
            {
                get { return operationFilter; }
            }

            private string serviceFilter;
            public string ServiceFilter
            {
                get { return serviceFilter; }
            }

            public WindowsMappingKey(string operationFilter, string serviceFilter)
            {
                this.operationFilter = operationFilter;
                this.serviceFilter = serviceFilter;
            }
        }
        #endregion

        #region Fields
        private const string UriDivider = "/";

        private List<WindowsMappingKey> keys;
        private Dictionary<WindowsMappingKey, List<string>> windowsMapping;
        private Dictionary<KeyValuePair<string, string>, bool> cachedResults;
        #endregion

        #region Ctors
        public WindowsAuthroziationManager()
        {
            if (Constants.WindowsAuthorizationEnableCaching)
            {
                cachedResults = new Dictionary<KeyValuePair<string, string>, bool>();
            }

            BuildCredMap();
        }
        #endregion

        #region Overrides
        protected override bool CheckAccessCore(OperationContext operationContext)
        {
            if (Constants.WindowsAuthorizationIsEnabled && ServiceSecurityContext.Current != null)
            {
                string action = operationContext.IncomingMessageHeaders.Action.ToLower();
                string userName = IdentityHelper.GetUsername();

                if (!string.IsNullOrEmpty(userName))
                {
                    KeyValuePair<string, string> key = new KeyValuePair<string, string>(action, userName);

                    if (Constants.WindowsAuthorizationEnableCaching)
                    {
                        bool stored;
                        lock (cachedResults)
                        {
                            if (cachedResults.TryGetValue(key, out stored))
                            {
                                return stored;
                            }
                        }
                    }

                    string operation = action;
                    string serviceName = GetServiceName(action);
                    if (Constants.WindowsAuthorizationProcessActionToOperationName)
                    {
                        operation = GetOperationName(operation);
                    }

                    bool result = IsActionAllowed(userName.ToLower(), operation, serviceName);

                    if (Constants.WindowsAuthorizationEnableCaching)
                    {
                        int capacity = Constants.WindowsAuthorizationCacheMaxEntriesCapacity;
                        lock (cachedResults)
                        {
                            if (cachedResults.Count > capacity && capacity > 0)
                            {
                                cachedResults.Clear();
                            }

                            cachedResults[key] = result;
                        }
                    }

                    return result;
                }
            }

            return base.CheckAccessCore(operationContext);
        }
        #endregion

        #region Methods
        private void BuildCredMap()
        {
            keys = new List<WindowsMappingKey>();
            windowsMapping = new Dictionary<WindowsMappingKey, List<string>>();

            WindowsAuthConfigurationSection windowsSection = WindowsAuthConfigurationSection.GetSection();

            if (windowsSection != null)
            {
                foreach (WindowsAuthElement item in windowsSection.Elements)
                {
                    string operation = item.OperationFilter ?? string.Empty;
                    string service = item.ServiceFilter ?? string.Empty;

                    operation = operation.ToLower();
                    service = service.ToLower();

                    string ntlm = item.Ntlm;

                    if (string.IsNullOrEmpty(ntlm))
                    {
                        throw new ArgumentException("NTLM cannot be null or empty.");
                    }

                    WindowsMappingKey key = new WindowsMappingKey(operation, service);
                    keys.Add(key);

                    List<string> ntlms;
                    if (!windowsMapping.TryGetValue(key, out ntlms))
                    {
                        ntlms = new List<string>();

                        windowsMapping.Add(key, ntlms);
                    }

                    string[] splitted = ntlm.Split(',');
                    foreach (string nt in splitted)
                    {
                        if (!string.IsNullOrEmpty(nt))
                        {
                            ntlms.Add(nt.Trim().ToLower());
                        }
                    }
                }
            }
        }

        private string GetOperationName(string action)
        {
            return GetUriPartByDividerAtLastIndex(action, UriDivider, false);
        }

        private string GetServiceName(string action)
        {
            action = GetUriPartByDividerAtLastIndex(action, UriDivider, true);
            action = GetUriPartByDividerAtLastIndex(action, UriDivider, false);

            return action;
        }

        private string GetUriPartByDividerAtLastIndex(string uri, string divider, bool first)
        {
            int lastIndex = uri.LastIndexOf(divider);

            if (lastIndex >= 0)
            {
                if (first)
                {
                    uri = uri.Substring(0, lastIndex);
                }
                else
                {
                    uri = uri.Substring(lastIndex + 1);
                }
            }

            return uri;
        }

        private List<string> GetWindowsSetting(string operation, string serviceName)
        {
            List<string> ntlms = null;

            WindowsMappingKey key = new WindowsMappingKey(operation, serviceName);
            if (!windowsMapping.TryGetValue(key, out ntlms))
            {
                for (int i = 0; i < keys.Count; i++)
                {
                    WindowsMappingKey storedKey = keys[i];

                    if ((storedKey.OperationFilter == operation || storedKey.OperationFilter + "Response" == operation)
                        && (storedKey.ServiceFilter == serviceName || string.IsNullOrEmpty(storedKey.ServiceFilter)))
                    {
                        ntlms = windowsMapping[storedKey];

                        break;
                    }
                }

                if (ntlms == null)
                {
                    key = new WindowsMappingKey(string.Empty, serviceName);
                    if (!windowsMapping.TryGetValue(key, out ntlms))
                    {
                        key = new WindowsMappingKey(string.Empty, string.Empty);

                        windowsMapping.TryGetValue(key, out ntlms);
                    }
                }
            }

            return ntlms;
        }

        private bool IsActionAllowed(string userName, string action, string serviceName)
        {
            List<string> windowsSetttings = GetWindowsSetting(action, serviceName);

            if (windowsSetttings != null)
            {
                bool found = (windowsSetttings.Contains(userName));

                if (!found && Constants.WindowsAuthorizationHandleRoles)
                {
                    foreach (string role in windowsSetttings)
                    {
                        if (IsUserInRole(role))
                        {
                            found = true;

                            break;
                        }
                    }
                }

                return found;
            }
            else if (Constants.WindowsAuthorizationDenyWhenUnmatched)
            {
                return false;
            }

            return true;
        }

        private static bool IsUserInRole(string role)
        {
            WindowsPrincipal principal = new WindowsPrincipal(ServiceSecurityContext.Current.WindowsIdentity);

            return principal.IsInRole(role);
        }
        #endregion
    }
}
